package gerenciador_memoria_virtual;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MemoriaPrincipal {

	public static final int TAMANHO_MEMORIA = 64;
	public static final int THREAD_MAIS_ANTIGA = 0;
	public static final int TOTAL_THREADS_MEMORIA = 16;
	public static final int TOTAL_PAGINAS_POR_THREAD = 4;

	// estrutura FIFO que gerencia quais sao as threads que estao na memoria
	private List<Integer> filaThreadsUsuarioMemoria;

	private HashMap<Integer, ArrayList<Integer>> tabelaThreadsCarregadas;
	private HashMap<Integer, Integer> indexadorFrames;
	private int threadsMemoria;

	public MemoriaPrincipal() {
		indexadorFrames = new HashMap<>();

		filaThreadsUsuarioMemoria = new ArrayList<>();

		tabelaThreadsCarregadas = new HashMap<>();
		iniciaTabelaThreadsCarregadas();

		threadsMemoria = 0;
	}

	public void iniciaTabelaThreadsCarregadas() {
		for (int i = 0; i < TOTAL_THREADS_MEMORIA; i++) {
			ArrayList<Integer> listaPaginas = new ArrayList<>();
			for (int j = 0; j < TOTAL_PAGINAS_POR_THREAD; j++) {
				listaPaginas.add(null);
			}
			tabelaThreadsCarregadas.put(null, listaPaginas);
		}
	}

	public void fifoThreadsCarregadas(int threadSerCarregada) {
		if (filaThreadsUsuarioMemoria.size() < TOTAL_THREADS_MEMORIA) {
			if (!filaThreadsUsuarioMemoria.contains(threadSerCarregada)) {
				filaThreadsUsuarioMemoria.add(threadSerCarregada);
			}
		} else {
			if (!filaThreadsUsuarioMemoria.contains(threadSerCarregada)) {
				filaThreadsUsuarioMemoria.remove(THREAD_MAIS_ANTIGA);
				filaThreadsUsuarioMemoria.add(threadSerCarregada);
			}
		}
	}

	// Metodo para alocacao da pagina
	public int alocarPaginaFrame(int idThread, int paginaSerAlocada,
			int paginaRemovida) {
		int frameMemoriaAlocado;
		int enderecoBase = -1;
		int offset = 0;

		// checa se a thread que deseja alocar uma pagina ja esta na memoria
		if (tabelaThreadsCarregadas.containsKey(idThread)) {
			// pega as paginas que esta determinada thread possui na memoria
			ArrayList<Integer> paginasCarregadas = tabelaThreadsCarregadas
					.get(idThread);

			// Se a pagina a ser alocada nao esta presente em nenhum dos 4
			// frames reservados para esta thread.
			if (!paginasCarregadas.contains(paginaSerAlocada)) {

				// ONDE OCORRE PAGE FAULT
				// caso onde os quatro frames estao ocupados, logo uma pagina
				// precisara sair para a nova entrar
				if (paginasCarregadas.size() == TOTAL_PAGINAS_POR_THREAD) {

					// Pego o endereco na memoria onde se encontra a pagina a
					// ser removida
					int indiceRemocaoPagina = paginasCarregadas
							.indexOf(paginaRemovida);

					// pego na tabela de paginas da thread , para essa pagina
					// que via entrar, qual o valor do offset
					offset = Gerenciador.getListaThreadsCriadas().get(idThread)
							.getTabelaPagina().get(paginaRemovida);

					// Removo a pagina do frame
					paginasCarregadas.remove(indiceRemocaoPagina);

					// Coloco a nova pagina exatamente no local da antiga
					paginasCarregadas.add(paginaSerAlocada);
				} else { // caso onde existe frame livre para a alocacao da nova
							// pagina

					// Apenas adiciono a nova pagina
					paginasCarregadas.add(paginaSerAlocada);
					// pego na tabela de paginas da thread , para essa pagina
					// que via entrar, qual o valor do offset
					offset = tabelaThreadsCarregadas.get(idThread).indexOf(
							paginaSerAlocada);
				}

				// atualiza a tabela com as threads
				tabelaThreadsCarregadas.put(idThread, paginasCarregadas);

			} else {
				offset = Gerenciador.getListaThreadsCriadas().get(idThread)
						.getTabelaPagina().get(paginaSerAlocada);
			}

			for (Map.Entry<Integer, Integer> parIndexadorFrame : indexadorFrames
					.entrySet()) {
				if (parIndexadorFrame.getValue() == idThread) {
					enderecoBase = parIndexadorFrame.getKey();
				}
			}
		} else {
			// caso onde a thread que deseja alocar uma pagina nao esta na
			// memoria

			// AQUI OCORRE O SWAP
			// se a quantidade de threads na memoria estiver no limite
			if (threadsMemoria == TOTAL_THREADS_MEMORIA) {

				// removemos a thread mais antiga
				int threadRemovida = filaThreadsUsuarioMemoria
						.get(THREAD_MAIS_ANTIGA);

				for (Map.Entry<Integer, Integer> parIndexadorFrame : indexadorFrames
						.entrySet()) {
					if (parIndexadorFrame.getValue() == threadRemovida) {
						enderecoBase = parIndexadorFrame.getKey();
					}
				}

				// Limpa os dados da thread apos ela
				Gerenciador.getListaThreadsCriadas().get(threadRemovida)
						.limparDados();

				// Remove a thread da memoria
				tabelaThreadsCarregadas.remove(threadRemovida);
				Gerenciador.setLog(Gerenciador.getLog() + "A Thread "
						+ idThread + " foi alocada e a Thread "
						+ threadRemovida + " foi removida.\n\n");
				System.out.println("A Thread " + idThread
						+ " foi alocada e a Thread " + threadRemovida
						+ " foi removida.");
				System.out.println();
				indexadorFrames.put(enderecoBase, idThread);

				// adiciona a thread na FIFO
				fifoThreadsCarregadas(idThread);

				// carrega a pagina no frame
				ArrayList<Integer> paginasCarregadas = new ArrayList<>();
				paginasCarregadas.add(paginaSerAlocada);

				// carrega a thread na memoria
				tabelaThreadsCarregadas.put(idThread, paginasCarregadas);
			} else { // Caso a memoria tenha espaco, apenas coloco a thread na
						// memoria

				// carrega a pagina no frame
				ArrayList<Integer> paginaCarregar = new ArrayList<>();
				paginaCarregar.add(paginaSerAlocada);

				// carrega a thread na memoria
				tabelaThreadsCarregadas.put(idThread, paginaCarregar);
				tabelaThreadsCarregadas.remove(null);

				// atualiza a FIFO
				fifoThreadsCarregadas(idThread);
				indexadorFrames.put(threadsMemoria * TOTAL_PAGINAS_POR_THREAD,
						idThread);
				enderecoBase = threadsMemoria * TOTAL_PAGINAS_POR_THREAD;
				threadsMemoria++;
			}
		}

		// CRIA O ENDERECO REAL DO FRAME ONDE FOI ALOCADA A PAGINA
		frameMemoriaAlocado = enderecoBase + offset;

		// retorna o endereco do frame onde a a pagina foi alocada
		return frameMemoriaAlocado;
	}

	public HashMap<Integer, ArrayList<Integer>> getTabelaThreadsCarregadas() {
		return tabelaThreadsCarregadas;
	}

	public HashMap<Integer, Integer> getIndexadorFrames() {
		return indexadorFrames;
	}
}
